package com.study.chunguard.common.utils.doc;

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.*;
import org.slf4j.Logger;
import org.springframework.util.StringUtils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 替换Doc段落和表格中的占位符
 *
 * @author zhaowenhao
 * @since 2022-07-21
 */
public class DocReplaceUtil {

    public static final Logger logger = org.slf4j.LoggerFactory.getLogger(DocReplaceUtil.class);

    /**
     * 前缀
     */
    public static final String PREFIX = "\\{｛";

    /**
     * 后缀
     */
    public static final String SUFFIX = "}｝";

    /**
     * 占位符正则表达式
     */
    public static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("[" + PREFIX + "]" + "(.+?)" + "[" + SUFFIX + "]", Pattern.CASE_INSENSITIVE);

    /**
     * 占位符前缀位于文本结尾的正则表达式
     */
    public static final Pattern PLACEHOLDER_PREFIX_AT_END_PATTERN = Pattern.compile("[" + PREFIX + "]" + "([^" + SUFFIX + "]*?)$", Pattern.CASE_INSENSITIVE);

    /**
     * 占位符后缀位于文本开头的正则表达式
     */
    public static final Pattern PLACEHOLDER_SUFFIX_AT_START_PATTERN = Pattern.compile("^([^" + PREFIX + "]*?)" + "[" + SUFFIX + "]", Pattern.CASE_INSENSITIVE);


    /**
     * 替换文档中的占位符
     *
     * @param sourceDocPath   需要替换的文档路径
     * @param replacedDocPath 替换后的文档路径
     * @param placeholderMap  替换的参数，key=占位符，value=实际值
     */
    public static void replaceInDoc(String sourceDocPath, String replacedDocPath, Map<String, Object> placeholderMap) throws IOException, InvalidFormatException {
        replaceInDoc(Files.newInputStream(Paths.get(sourceDocPath)), Files.newOutputStream(Paths.get(replacedDocPath)), placeholderMap);
    }

    /**
     * 替换文档中的占位符
     *
     * @param sourceDocPath           需要替换的文档路径
     * @param replacedDocOutputStream 替换后的文档输出流
     * @param placeholderMap          替换的参数，key=占位符，value=实际值
     */
    public static void replaceInDoc(String sourceDocPath, OutputStream replacedDocOutputStream, Map<String, Object> placeholderMap) throws IOException, InvalidFormatException {
        replaceInDoc(Files.newInputStream(Paths.get(sourceDocPath)), replacedDocOutputStream, placeholderMap);
    }

    /**
     * 替换文档中的占位符
     *
     * @param sourceDocInputStream 需要替换的文档输入流
     * @param replacedDocPath      替换后的文档路径
     * @param placeholderMap       替换的参数，key=占位符，value=实际值
     */
    public static void replaceInDoc(InputStream sourceDocInputStream, String replacedDocPath, Map<String, Object> placeholderMap) throws IOException, InvalidFormatException {
        replaceInDoc(sourceDocInputStream, Files.newOutputStream(Paths.get(replacedDocPath)), placeholderMap);
    }

    /**
     * 替换文档中的占位符
     *
     * @param sourceDocInputStream    需要替换的文档输入流
     * @param replacedDocOutputStream 替换后的文档输出流
     * @param placeholderMap          替换的参数，key=占位符，value=实际值
     */
    public static void replaceInDoc(InputStream sourceDocInputStream, OutputStream replacedDocOutputStream, Map<String, Object> placeholderMap) throws IOException, InvalidFormatException {
        XWPFDocument doc = new XWPFDocument(OPCPackage.open(sourceDocInputStream));
        // 获取文档中所有的段落
        Iterator<XWPFParagraph> paragraphsIterator = doc.getParagraphsIterator();
        while (paragraphsIterator.hasNext()) {
            XWPFParagraph paragraph = paragraphsIterator.next();
            replaceInParagraph(paragraph, placeholderMap);
        }

        // 获取文档中所有的表格
        Iterator<XWPFTable> tablesIterator = doc.getTablesIterator();
        while (tablesIterator.hasNext()) {
            XWPFTable table = tablesIterator.next();
            for (XWPFTableRow tableRow : table.getRows()) {
                for (XWPFTableCell tableCell : tableRow.getTableCells()) {
                    for (XWPFParagraph paragraph : tableCell.getParagraphs()) {
                        replaceInParagraph(paragraph, placeholderMap);
                    }
                }
            }
        }
        doc.write(replacedDocOutputStream);
    }

    /**
     * 替换段落中的占位符
     *
     * @param paragraph      需要替换的段落
     * @param placeholderMap 替换的参数，key=占位符，value=实际值
     */
    private static void replaceInParagraph(XWPFParagraph paragraph, Map<String, Object> placeholderMap) {
        XWPFRun[] xwpfRuns = paragraph.getRuns().toArray(new XWPFRun[0]);
        // 该双向队列存放一系列由（占位符前缀位于文本结尾的run序号，占位符后缀位于文本开头的run序号）构成的组，用于后续的提取占位符信息及替换操作
        Deque<int[]> placeholderRunGroups = new LinkedList<>();
        for (int i = 0; i < xwpfRuns.length; i++) {
            XWPFRun xwpfRun = xwpfRuns[i];
            String text = xwpfRun.getText(0);
            if (StringUtils.hasLength(text)) {
                if (PLACEHOLDER_PATTERN.matcher(text).find()) {
                    replaceInRun(xwpfRun, text, placeholderMap);
                    text = checkEmpty(xwpfRun.getText(0));
                }
                // 上次匹配到的是否为占位符前缀，不为空且必须为默认状态即组头序号和尾序号相同
                boolean isPrefixLastMatched = !placeholderRunGroups.isEmpty() && (placeholderRunGroups.getLast()[0] == placeholderRunGroups.getLast()[1]);
                if (PLACEHOLDER_PREFIX_AT_END_PATTERN.matcher(text).find() && (!PLACEHOLDER_SUFFIX_AT_START_PATTERN.matcher(text).find())) {
                    if (!isPrefixLastMatched) {
                        // 默认组头序号和尾序号相同
                        int[] placeholderRunGroup = {i, i};
                        placeholderRunGroups.add(placeholderRunGroup);
                    }
                }
                if (PLACEHOLDER_SUFFIX_AT_START_PATTERN.matcher(text).find() && (!PLACEHOLDER_PREFIX_AT_END_PATTERN.matcher(text).find())) {
                    if (isPrefixLastMatched) {
                        // 如果是，补上占位符后缀
                        placeholderRunGroups.getLast()[1] = i;
                    }
                }
            }

        }
        // 处理组，提取占位符信息，替换占位符
        placeholderRunGroups.forEach(placeholderRunGroup -> {
            int start = placeholderRunGroup[0];
            int end = placeholderRunGroup[1];
            StringBuilder sb = new StringBuilder();
            for (int i = start; i <= end; i++) {
                sb.append(checkEmpty(xwpfRuns[i].getText(0)));
                if (i > start) {
                    xwpfRuns[i].setText("", 0);
                }
            }
            replaceInRun(xwpfRuns[start], sb.toString(), placeholderMap);
        });
    }

    /**
     * 替换run中的占位符
     *
     * @param xwpfRun        需要替换的run
     * @param sourceText     原文本
     * @param placeholderMap 替换的参数，key=占位符，value=实际值
     */
    private static void replaceInRun(XWPFRun xwpfRun, String sourceText, Map<String, Object> placeholderMap) {
        Matcher matcher = PLACEHOLDER_PATTERN.matcher(sourceText);
        // 保存文字
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            String key = matcher.group(1);
            // 默认图片长宽为100px
            String width = "300";
            String height = "200";
            if (key.contains("#")) {
                // 解析key和长宽（{图片-身份证#90#60}）
                String[] keys = key.split("#");
                if (keys.length == 1) {
                    key = keys[0];
                } else if (keys.length == 2) {
                    key = keys[0];
                    width = keys[1];
                    height = keys[1];
                } else if (keys.length >= 3) {
                    key = keys[0];
                    width = keys[1];
                    height = keys[2];
                }
            }
            // 执行替换
            if (placeholderMap.containsKey(key)) {
                Object actualValue = placeholderMap.get(key);
                if (!(actualValue instanceof byte[])) {
                    // 非图片输入流
                    matcher.appendReplacement(sb, checkEmpty(String.valueOf(actualValue)));
                } else {
                    // 图片输入流
                    try {
                        matcher.appendReplacement(sb, "");
                        xwpfRun.addPicture(new ByteArrayInputStream((byte[]) actualValue), XWPFDocument.PICTURE_TYPE_PNG, "图片", Units.toEMU(Integer.parseInt(width)), Units.toEMU(Integer.parseInt(height)));
                    } catch (Exception e) {
                        logger.warn("插入图片失败", e);
                    }
                }
            }
        }
        matcher.appendTail(sb);
        xwpfRun.setText(sb.toString(), 0);
    }

    /**
     * 检查的null或空字符串文本，并替换为空字符串
     *
     * @param text 需要检查的文本
     * @return 替换后的文本
     */
    private static String checkEmpty(String text) {
        return StringUtils.hasLength(text) && (!("null".equals(text))) ? text : "";
    }
}
